package com.rc.saas.mini.controller.recharge;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.rc.saas.base.MinipBaseController;
import com.rc.saas.mini.client.ChargerClientRouter;
import com.rc.saas.mini.common.base.DataTResponse;
import com.rc.saas.mini.common.base.TResponse;
import com.rc.saas.mini.common.constant.HttpConstant;
import com.rc.saas.mini.component.UidGenerator;
import com.rc.saas.mini.minip.vo.LoginInfoVo;
import com.rc.saas.mini.model.member.MemberAccount;
import com.rc.saas.mini.model.member.MemberInfo;
import com.rc.saas.mini.model.recharge.RechargeOrder;
import com.rc.saas.mini.model.tenant.Tenant;
import com.rc.saas.mini.service.member.MemberAccountService;
import com.rc.saas.mini.service.member.MemberInfoService;
import com.rc.saas.mini.service.recharge.RechargeOrderService;
import com.rc.saas.mini.service.tenant.TenantService;
import com.rc.saas.mini.vo.recharge.RechargeOrderVo;
import com.rc.saas.mini.weixin.RedisWxConfiguration;
import com.rc.saas.mini.weixin.RedisWxConfigurationLoader;
import com.rc.saas.payment.wxpay.WXPayConstants;
import com.rc.saas.payment.wxpay.WxPayRetVo;
import com.rc.saas.payment.wxpay.XmlBuilder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.weixin4j.miniprogram.WeixinMiniprogram;
import org.weixin4j.miniprogram.component.PayComponent;
import org.weixin4j.miniprogram.model.pay.PayNotifyResult;
import org.weixin4j.miniprogram.model.pay.UnifiedOrder;
import org.weixin4j.miniprogram.model.pay.UnifiedOrderResult;
import org.weixin4j.miniprogram.model.pay.WCPay;
import org.weixin4j.miniprogram.util.PayUtil;
import org.weixin4j.miniprogram.util.SignUtil;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;

/**
 * @author wangzhengwei
 * @create 2021/4/29
 */
@Controller
@RequestMapping("/wxPayMiniRefund")
@Api(tags = "12-1.充值.小程序（（先付后退））")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class WxPayRefundMiniController
        extends MinipBaseController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    //支付方式 0微信支付  1支付宝
    String PAYMENTTYPE = "0";

    //微信支付后端回调通知地址
    @Value("${weChat.baseUrl}")
    private String baseUrl;

    @Autowired
    private MemberInfoService memberInfoService;

    @Autowired
    private MemberAccountService memberAccountService;

    @Autowired
    private UidGenerator uidGenerator;

    @Autowired
    private RechargeOrderService rechargeOrderService;

    @Autowired
    private TenantService tenantService;

    @Autowired
    private RedisWxConfigurationLoader redisWxConfigurationLoader;

    @Autowired
    private ChargerClientRouter chargerClientRouter;

    /**
     * 执行微信支付预下单
     *
     * @return 预下单结果(成功时返回前端js提交数据)
     */
    @ResponseBody
    @RequestMapping(value = "/prePay", method = RequestMethod.POST)
    @ApiOperation(value = "执行微信支付预下单", httpMethod = "POST", produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "amount", paramType = "query", value = "充值金额", required = true),
            //@ApiImplicitParam(name = "factAmount", paramType = "query", value = "实际支付金额", required = true),
            //@ApiImplicitParam(name = "giveAmount", paramType = "query", value = "赠送金额", required = true),
    })
    public DataTResponse<WCPay> prePay(@ApiIgnore HttpServletRequest request, @ApiIgnore HttpSession session, BigDecimal amount, BigDecimal factAmount, BigDecimal giveAmount) {

        LoginInfoVo loginInfoVo = readLoginInfo(request);
        if (loginInfoVo == null) {
            return DataTResponse.FAIL_LOGIN();
        }
        if(loginInfoVo.getMiniOpenid()==null){
            return DataTResponse.FAIL("未绑定微信");
        }
        String tenantCode = loginInfoVo.getTenantCode();
        //WxSessionInfo wxSessionInfo = getWxSessionInfo(loginInfoVo);

        Tenant tenant = tenantService.selectByPrimaryKey(tenantCode);


        if (tenantCode == null || StringUtils.isBlank(tenantCode)) {
            logger.error("找不到对应运营商信息:{}", tenantCode);
            return DataTResponse.FAIL("找不到对应运营商信息");
        }
        RedisWxConfiguration redisWxConfiguration = redisWxConfigurationLoader.get(tenant.getMinipOriginalid());
        if (redisWxConfiguration == null) {
            return DataTResponse.FAIL("用户配置不正确");
        }

        MemberInfo member = memberInfoService.selectByPrimaryKey(loginInfoVo.getMemberId());
        if (member == null || StringUtils.isBlank(member.getMobile())) {
            return DataTResponse.FAIL_LOGIN();
        }

        //logger.info("wxpay.prePay.unionid:{},openid:{},tenantCode:{}:mobile:{}", wxSessionInfo.getUnionid(), wxSessionInfo.getOpenid(), wxSessionInfo.getTenantCode(), member.getMobile());

        //检查member_account 时候有数据  没有则新增
        MemberAccount memberAccount = memberAccountService.selectByMemberId(member.getTenantCode(), member.getMemberId());
        if (memberAccount == null) {
            MemberAccount memberAccountNew = new MemberAccount();
            memberAccountNew.setMemberId(member.getMemberId());
            memberAccountNew.setTenantCode(tenantCode);
            memberAccountNew.setCreatedTime(new Date());
            memberAccountNew.setLastUpdatedTime(new Date());
            memberAccountService.insertSelective(memberAccountNew);
        }


        if(memberAccount==null||memberAccount.getPaymentRefundAccount().compareTo(BigDecimal.ZERO)>0){
            return DataTResponse.FAIL("先付后退钱包还存在余额");
        }
        logger.info("saas微信支付预下单:{},amount:{},factAmount:{},giveAmount:{}", member.getMobile(), amount, factAmount, giveAmount);

        if ("1".equals(member.getStatus())) {
            return DataTResponse.FAIL("会员已被冻结");
        }

        //订单总金额，单位为分
        //交易金额默认为人民币交易，接口中参数支付金额单位为【分】，参数值不能带小数。对账单中的交易金额单位为【元】。
        //外币交易的支付金额精确到币种的最小单位，参数值不能带小数点
        BigDecimal targetAmount = amount.multiply(new BigDecimal("100")).setScale(0, BigDecimal.ROUND_DOWN);
        if (targetAmount.compareTo(BigDecimal.ZERO) < 1) {
            return DataTResponse.FAIL("factAmount金额不合法");
        }

        //根据不同的运营商简码生成充值订单号
        String outTradeNo = uidGenerator.genWxRechargeOutTradeNo(tenant.getTenantPrefix(), member.getMobile());
        if (outTradeNo == null) {
            return DataTResponse.FAIL("无法生成充值订单号");
        }

        RechargeOrder order = new RechargeOrder();
        order.setTenantCode(tenant.getTenantCode());
        order.setAmount(amount);
        order.setFactAmount(amount);
        //order.setGiveAmount(giveAmount);
        //充值方式  0 公众号 1 APP  2小程序
        order.setRechargeType("2");
        order.setOutTradeNo(outTradeNo);
        order.setMemberId(member.getMemberId());
        //支付方式 0:微信支付 1:支付宝
        order.setPaymentType("0");
        order.setPaymentProperty("1");//0预充值 1先付后退
        //到帐状态 0未到帐   1已到帐 默认0
        order.setStatus("0");
        order.setCreatedTime(new Date());
        order.setLastUpdatedTime(new Date());
        order.setCreatedBy(String.valueOf(member.getMemberId()));
        order.setLastUpdatedBy(String.valueOf(member.getMemberId()));
        rechargeOrderService.insertSelective(order);

        //生成微信统一订单
        UnifiedOrder unifiedOrder = new UnifiedOrder();
        unifiedOrder.setAppid(tenant.getMinipAppid());
        unifiedOrder.setMch_id(redisWxConfiguration.getMchId());
        unifiedOrder.setNonce_str(UUID.randomUUID().toString().replace("-", ""));
        unifiedOrder.setBody(tenant.getWechatName() + "-充值");
        unifiedOrder.setOut_trade_no(outTradeNo);
        unifiedOrder.setTotal_fee(targetAmount.toPlainString());
        unifiedOrder.setSpbill_create_ip(request.getRemoteHost());
        unifiedOrder.setNotify_url(baseUrl + "wxPayMiniRefund/open/payNotify");
        unifiedOrder.setTrade_type("JSAPI");
        unifiedOrder.setOpenid(member.getMinipOpenid());

        //附加数据/自定义参数,异步通知时可以原样返回
        unifiedOrder.setAttach(String.valueOf(member.getMemberId()));
        logger.info("unifiedOrder.toMap:{}", unifiedOrder.toMap());
        logger.info("redisWxConfiguration.PartnerKey:{}", redisWxConfiguration.getPartnerKey());
        unifiedOrder.setSign(SignUtil.getSign(unifiedOrder.toMap(), redisWxConfiguration.getPartnerKey()));

        try {
            ObjectMapper objectMapper = new ObjectMapper();
            logger.info("订单信息 unifiedOrder:{}", objectMapper.writeValueAsString(unifiedOrder));
            //初始化weixin对象
            WeixinMiniprogram weixin = new WeixinMiniprogram(redisWxConfiguration.getAppId(), redisWxConfiguration.getSecret());

            //统一预下单
            PayComponent PayComponent = new PayComponent(weixin);
            UnifiedOrderResult result = PayComponent.payUnifiedOrder(unifiedOrder);

            logger.info("支付结果 UnifiedOrderResult:{}", objectMapper.writeValueAsString(result));

            if (!result.isSuccess()) {
                return DataTResponse.FAIL("预下单失败");
            }
            logger.info("预下单成功:{}", unifiedOrder.toMap());

            //对预下单Id进行H5支付
            WCPay wcPay = PayUtil.getBrandWCPayRequest(tenant.getMinipAppid(), result.getPrepay_id(), redisWxConfiguration.getPartnerKey());
            //TResponse tResponse = TResponse.SUCCESS();
            //tResponse.put(wcPay);
            return DataTResponse.SUCCESS_DATA(wcPay);
        } catch (Exception e) {
            logger.error("微信支付预下单异常", e);
            return DataTResponse.FAIL("操作失败");
        }
    }

    /**
     * 接收微信支付回调请求
     * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
     * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_7&index=3
     * SUCCESS/FAIL,SUCCESS表示商户接收通知成功并校验成功
     * 对后台通知交互时，如果微信收到商户的应答不是成功或超时，微信认为通知失败，
     * 微信会通过一定的策略定期重新发起通知，尽可能提高通知的成功率，但微信不保证通知最终能成功。 （通知频率为15/15/30/180/1800/1800/1800/1800/3600，单位：秒）
     *
     * @param result 获取微信返回数据(全部以字符串处理,后解析为xml)
     * @return 处理结果以xml回复微信服务器
     */
    @ApiIgnore
    @ResponseBody
    @RequestMapping("/open/payNotify")
    public synchronized String payNotify(@RequestBody String result, HttpServletRequest request) {
        String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getServletPath();
        logger.info("wxPayNotify异步通知:{}", url);
        logger.info("接收微信支付异步通知数据:{}", result);

        //特别提醒：
        //1、商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致，防止数据泄漏导致出现“假通知”，造成资金损失。
        //2、当收到通知进行处理时，首先检查对应业务数据的状态，判断该通知是否已经处理过，如果没有处理过再进行处理，如果处理过直接返回结果成功。
        //3、在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱。
        Long memberId = null;
        try {
            JAXBContext context = JAXBContext.newInstance(PayNotifyResult.class);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            PayNotifyResult payNotifyResult = Objects.requireNonNull((PayNotifyResult) unmarshaller.unmarshal(new StringReader(result)));
            logger.info("payResult----->:{}", payNotifyResult);

            logger.info("pay.payNotify.getResult_code()----->:{},getReturn_code():{}", payNotifyResult.getResult_code(), payNotifyResult.getReturn_code());

            //是否支付成功
            if (!"SUCCESS".equals(payNotifyResult.getResult_code()) && !"SUCCESS".equals(payNotifyResult.getReturn_code())) {
                logger.error("result_code/return_code标记不为SUCCESS,result_code:{},return_code:{}", payNotifyResult.getResult_code(), payNotifyResult.getReturn_code());
                return voToXml(new WxPayRetVo(WXPayConstants.FAIL, "result_code/return_code标记不为SUCCESS"));
            }

            String out_trade_no = payNotifyResult.getOut_trade_no();
            String transaction_id = payNotifyResult.getTransaction_id();
            logger.info("payResult.getTransaction_id()----->:{},getOut_trade_no:{}", transaction_id, out_trade_no);

            RechargeOrderVo rechargeOrder = rechargeOrderService.selectRechargeOrderByOutTradeNo(payNotifyResult.getOut_trade_no());
            if (null != rechargeOrder) {
                RedisWxConfiguration redisWxConfiguration = redisWxConfigurationLoader.get(rechargeOrder.getMinipOriginalid());
                if (null == redisWxConfiguration) {
                    logger.error("微信支付回调处理失败找不到对应运营商:{},:{}", out_trade_no, rechargeOrder.getMinipOriginalid());
                    return voToXml(new WxPayRetVo(WXPayConstants.FAIL, "微信支付回调失败找不到对应运营商"));
                }

                //1、商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致，防止数据泄漏导致出现“假通知”，造成资金损失。
                if (null == payNotifyResult.getCash_fee()) {
                    if (!PayUtil.verifySign(result, redisWxConfiguration.getPartnerKey())) {
                        logger.error("微信支付回调处理,获取签名失败:{}", out_trade_no);
                        return voToXml(new WxPayRetVo(WXPayConstants.FAIL, "签名失败"));
                    }
                }

                //2、会员验证
                if (!payNotifyResult.getAttach().equals(String.valueOf(rechargeOrder.getMemberId()))) {
                    logger.error("入账会员ID不一致:{},:{}", payNotifyResult.getOut_trade_no(), payNotifyResult.getAttach() + ":" + rechargeOrder.getMemberId());
                    return voToXml(new WxPayRetVo(WXPayConstants.FAIL, "入账会员ID不一致"));
                }

                //3、金额验证,订单金额是否与商户侧的订单金额一致
                //支付金额：将单位由"元"转为"分"进行比较
                BigDecimal targetAmount = rechargeOrder.getFactAmount().multiply(new BigDecimal("100").setScale(0, BigDecimal.ROUND_DOWN));

                if (targetAmount.compareTo(new BigDecimal(payNotifyResult.getTotal_fee())) != 0) {
                    logger.error("入账金额不一致,:{},{},{}", rechargeOrder.getMemberId(), payNotifyResult.getOut_trade_no(), targetAmount + ":" + payNotifyResult.getTotal_fee());
                    return voToXml(new WxPayRetVo(WXPayConstants.FAIL, "入账金额不一致"));
                }

                boolean flag=false;
                //到帐状态 0未到账 1已到账 默认0
                if (!HttpConstant.STATUS_ONE.equals(rechargeOrder.getStatus())) {
                    memberId = rechargeOrder.getMemberId();
                    //入账处理

                    flag=rechargeOrderService.updateRechargeOrderRefund(rechargeOrder.getRechargeOrderId(), PAYMENTTYPE, transaction_id);
                    logger.info("****微信支付回调处理成功！****运营商tenantCodee:{}，充值用户ID:{},订单号:{},:{}",
                            rechargeOrder.getTenantCode(), rechargeOrder.getMemberId(), out_trade_no);
                }

                if (flag) {
                    return voToXml(new WxPayRetVo(WXPayConstants.SUCCESS, "OK"));
                }else{
                    return voToXml(new WxPayRetVo(WXPayConstants.FAIL, "处理失败"));
                }
            } else {
                logger.error("微信支付回调处理失败找不到对应订单:{}", out_trade_no);
                return voToXml(new WxPayRetVo(WXPayConstants.FAIL, "微信支付回调处理失败找不到对应订单"));
            }

        } catch (JAXBException e) {
            logger.error("微信支付回调处理失败", e);
            return voToXml(new WxPayRetVo(WXPayConstants.FAIL, "处理失败"));
        } finally {
            //下发用户余额变动记录
            if (memberId != null) {
                chargerClientRouter.sendUserAccount(memberId, null);
            }
        }

    }

    /**
     * WxRetVo转XML
     */
    private String voToXml(WxPayRetVo wxRetVo) {
        //将java对象转换为XML字符串
        return XmlBuilder.convertToXml(wxRetVo);
    }

}
